package com.aaron.gesturehelper.gesturelib;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;

public class MyGesture {

	private static final long GESTURE_ID_BASE = System.currentTimeMillis();

	private static final int BITMAP_RENDERING_WIDTH = 2;

	private static final boolean BITMAP_RENDERING_ANTIALIAS = true;
	private static final boolean BITMAP_RENDERING_DITHER = true;

	private static final AtomicInteger sGestureCount = new AtomicInteger(0);

	private final RectF mBoundingBox = new RectF();

	// the same as its instance ID
	private long mGestureID;

	private final ArrayList<MyGestureStroke> mStrokes = new ArrayList<MyGestureStroke>();

	public MyGesture() {
		mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
	}

	@Override
	public Object clone() {
		MyGesture gesture = new MyGesture();
		gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top,
				mBoundingBox.right, mBoundingBox.bottom);
		final int count = mStrokes.size();
		for (int i = 0; i < count; i++) {
			MyGestureStroke stroke = mStrokes.get(i);
			gesture.mStrokes.add((MyGestureStroke) stroke.clone());
		}
		return gesture;
	}

	/**
	 * @return all the strokes of the gesture
	 */
	public ArrayList<MyGestureStroke> getStrokes() {
		return mStrokes;
	}

	/**
	 * @return the number of strokes included by this gesture
	 */
	public int getStrokesCount() {
		return mStrokes.size();
	}

	/**
	 * Adds a stroke to the gesture.
	 * 
	 * @param stroke
	 */
	public void addStroke(MyGestureStroke stroke) {
		mStrokes.add(stroke);
		mBoundingBox.union(stroke.boundingBox);
	}

	/**
	 * Calculates the total length of the gesture. When there are multiple
	 * strokes in the gesture, this returns the sum of the lengths of all the
	 * strokes.
	 * 
	 * @return the length of the gesture
	 */
	public float getLength() {
		int len = 0;
		final ArrayList<MyGestureStroke> strokes = mStrokes;
		final int count = strokes.size();

		for (int i = 0; i < count; i++) {
			len += strokes.get(i).length;
		}

		return len;
	}

	/**
	 * @return the bounding box of the gesture
	 */
	public RectF getBoundingBox() {
		return mBoundingBox;
	}

	public Path toPath() {
		return toPath(null);
	}

	public Path toPath(Path path) {
		if (path == null)
			path = new Path();

		final ArrayList<MyGestureStroke> strokes = mStrokes;
		final int count = strokes.size();

		for (int i = 0; i < count; i++) {
			path.addPath(strokes.get(i).getPath());
		}

		return path;
	}

	public Path toPath(int width, int height, int edge, int numSample) {
		return toPath(null, width, height, edge, numSample);
	}

	public Path toPath(Path path, int width, int height, int edge, int numSample) {
		if (path == null)
			path = new Path();

		final ArrayList<MyGestureStroke> strokes = mStrokes;
		final int count = strokes.size();

		for (int i = 0; i < count; i++) {
			path.addPath(strokes.get(i).toPath(width - 2 * edge,
					height - 2 * edge, numSample));
		}

		return path;
	}

	/**
	 * Sets the id of the gesture.
	 * 
	 * @param id
	 */
	void setID(long id) {
		mGestureID = id;
	}

	/**
	 * @return the id of the gesture
	 */
	public long getID() {
		return mGestureID;
	}

	/**
	 * Creates a bitmap of the gesture with a transparent background.
	 * 
	 * @param width
	 *            width of the target bitmap
	 * @param height
	 *            height of the target bitmap
	 * @param edge
	 *            the edge
	 * @param numSample
	 * @param color
	 * @return the bitmap
	 */
	public Bitmap toBitmap(int width, int height, int edge, int numSample,
			int color) {
		final Bitmap bitmap = Bitmap.createBitmap(width, height,
				Bitmap.Config.ARGB_8888);
		final Canvas canvas = new Canvas(bitmap);

		canvas.translate(edge, edge);

		final Paint paint = new Paint();
		paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
		paint.setDither(BITMAP_RENDERING_DITHER);
		paint.setColor(color);
		paint.setStyle(Paint.Style.STROKE);
		paint.setStrokeJoin(Paint.Join.ROUND);
		paint.setStrokeCap(Paint.Cap.ROUND);
		paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);

		final ArrayList<MyGestureStroke> strokes = mStrokes;
		final int count = strokes.size();

		for (int i = 0; i < count; i++) {
			Path path = strokes.get(i).toPath(width - 2 * edge,
					height - 2 * edge, numSample);
			canvas.drawPath(path, paint);
		}

		return bitmap;
	}

	/**
	 * Creates a bitmap of the gesture with a transparent background.
	 * 
	 * @param width
	 * @param height
	 * @param inset
	 * @param color
	 * @return the bitmap
	 */
	public Bitmap toBitmap(int width, int height, int inset, int color) {
		final Bitmap bitmap = Bitmap.createBitmap(width, height,
				Bitmap.Config.ARGB_8888);
		final Canvas canvas = new Canvas(bitmap);

		final Paint paint = new Paint();
		paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
		paint.setDither(BITMAP_RENDERING_DITHER);
		paint.setColor(color);
		paint.setStyle(Paint.Style.STROKE);
		paint.setStrokeJoin(Paint.Join.ROUND);
		paint.setStrokeCap(Paint.Cap.ROUND);
		paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);

		final Path path = toPath();
		final RectF bounds = new RectF();
		path.computeBounds(bounds, true);

		final float sx = (width - 2 * inset) / bounds.width();
		final float sy = (height - 2 * inset) / bounds.height();
		final float scale = sx > sy ? sy : sx;
		paint.setStrokeWidth(2.0f / scale);

		path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
				-bounds.top + (height - bounds.height() * scale) / 2.0f);

		canvas.translate(inset, inset);
		canvas.scale(scale, scale);

		canvas.drawPath(path, paint);

		return bitmap;
	}

	void serialize(DataOutputStream out) throws IOException {
		final ArrayList<MyGestureStroke> strokes = mStrokes;
		final int count = strokes.size();

		// Write gesture ID
		out.writeLong(mGestureID);
		// Write number of strokes
		out.writeInt(count);

		for (int i = 0; i < count; i++) {
			strokes.get(i).serialize(out);
		}
	}

	static MyGesture deserialize(DataInputStream in) throws IOException {
		final MyGesture gesture = new MyGesture();

		// Gesture ID
		gesture.mGestureID = in.readLong();
		// Number of strokes
		final int count = in.readInt();

		for (int i = 0; i < count; i++) {
			gesture.addStroke(MyGestureStroke.deserialize(in));
		}

		return gesture;
	}
}
